package gui.component;

import database.DatabaseWorker;
import database.entity.Contact;
import database.entity.ContactGroup;
import database.entity.User;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.Timer;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;

public class AutoCompleteCombobox extends JComboBox
{   
    private Model model;
    private final JTextComponent textComponent = (JTextComponent) getEditor().getEditorComponent();
    private boolean modelFilling = false;
    private boolean updatePopup;
    
    private final ContactChooser chooser;

    public AutoCompleteCombobox(ContactChooser contactChooser)
    {
        chooser=contactChooser;
        setEditable(true);
        setPattern(null);
        updatePopup = false;

        model = new Model(contactChooser.user);
        textComponent.setDocument(new AutoCompleteDocument());
        setModel(model);
        setSelectedItem(null);

        new Timer(20, (ActionEvent e) ->
        {
            if (updatePopup && isDisplayable())
            {
                setPopupVisible(false);
                if (model.getSize() > 0)
                {
                    setPopupVisible(true);
                }
                updatePopup = false;
            }
        }).start();
    }

    private class AutoCompleteDocument extends PlainDocument
    {
        boolean arrowKeyPressed = false;
        public AutoCompleteDocument()
        {
            textComponent.addKeyListener(new KeyAdapter()
            {
                @Override
                public void keyPressed(KeyEvent e)
                {
                    int key = e.getKeyCode();
                    if (key == KeyEvent.VK_ENTER)
                    {                        
                        //there is no such element in the model for now
                        String text = textComponent.getText();
                        if (!"".equals(text))
                        {
                            chooser.addContact(text);
                            AutoCompleteCombobox.this.setText("");
                        }
                    }
                    else if (key == KeyEvent.VK_UP
                            || key == KeyEvent.VK_DOWN)
                    {
                        arrowKeyPressed = true;                        
                    }
                }
            });
        }

        void updateModel() throws BadLocationException
        {
            String textToMatch = getText(0, getLength());            
            setPattern(textToMatch);
        }

        @Override
        public void remove(int offs, int len) throws BadLocationException
        {
            if (modelFilling)
            {
                //logger.debug("[remove] model is being filled now");
                return;
            }
            super.remove(offs, len);
            if (arrowKeyPressed)
            {
                arrowKeyPressed = false;
                //logger.debug("[remove] arrow key was pressed, updateModel() was NOT called");
            }
            else
            {
                //logger.debug("[remove] calling updateModel()");
                updateModel();
            }
            clearSelection();
        }

        @Override
        public void insertString(int offs, String str, AttributeSet a) throws BadLocationException
        {
            if (modelFilling)
            {
                //logger.debug("[insert] model is being filled now");
                return;
            }
            // insert the string into the document
            super.insertString(offs, str, a);

            String text = getText(0, getLength());
            if (arrowKeyPressed)
            {
                //logger.debug("[insert] arrow key was pressed, updateModel() was NOT called");
                model.setSelectedItem(text);
                //logger.debug(String.format("[insert] model.setSelectedItem(%s)", text));
                arrowKeyPressed = false;
            }
            else if (!text.equals(getSelectedItem()))
            {
                //logger.debug("[insert] calling updateModel()");
                updateModel();
            }
            clearSelection();
        }
    }

    public void setText(String text)
    {
        if (model.data.contains(text))
        {
            setSelectedItem(text);
        }
    }

    public String getText()
    {
        return getEditor().getItem().toString();
    }
    private String previousPattern = null;

    private void setPattern(String pattern)
    {

        if (pattern != null && pattern.trim().isEmpty())
        {
            pattern = null;
        }

        if (previousPattern == null && pattern == null
                || pattern != null && pattern.equals(previousPattern))
        {            
            return;
        }
        
        previousPattern = pattern;
        modelFilling = true;
        model.setPattern(pattern);

        modelFilling = false;
        if (pattern != null)
        {
            updatePopup = true;
        }
    }

    private void clearSelection()
    {
        int i = getText().length();
        textComponent.setSelectionStart(i);
        textComponent.setSelectionEnd(i);
    }

    private class Model extends AbstractListModel implements ComboBoxModel
    {
        Data data = new Data();
        String selected;
        final String delimiter = ";;;";
        final int limit = 20;        

        public Model(User user)
        {
            readData(user);
        }
        
        private void readData(User user)
        {                
            //add all contacts here
            ContactGroup[] ctgs = user.databaseWorker.getAllContactGroup(false);
            for (ContactGroup ctg : ctgs)
            {
                user.databaseWorker.loadContactFromGroup(ctg, true);
                for(Contact ct: ctg.contacts)
                {
                    data.add(ct.ContactName + "<" +ct.Email+ ">");
                }
            }            
        }             
        
        public void setPattern(String pattern)
        {
            int size1 = getSize();
            data.setPattern(pattern);
            int size2 = getSize();
            if (size1 < size2)
            {
                fireIntervalAdded(this, size1, size2 - 1);
                fireContentsChanged(this, 0, size1 - 1);
            }
            else if (size1 > size2)
            {
                fireIntervalRemoved(this, size2, size1 - 1);
                fireContentsChanged(this, 0, size2 - 1);
            }
        }      

        @Override
        public Object getSelectedItem()
        {
            return selected;
        }

        @Override
        public void setSelectedItem(Object anObject)
        {
            if ((selected != null && !selected.equals(anObject))
                    || selected == null && anObject != null)
            {
                selected = (String) anObject;
                fireContentsChanged(this, -1, -1);
            }
        }

        @Override
        public int getSize()
        {
            return data.getFiltered().size();
        }

        @Override
        public Object getElementAt(int index)
        {
            return data.getFiltered().get(index);
        }
        
        class Data
        {
            private final List<String> list = new ArrayList<>(limit);
            private final List<String> lowercase = new ArrayList<>(limit);
            private List<String> filtered;

            void add(String s)
            {
                list.add(s);
                lowercase.add(s.toLowerCase());
            }

            void addToTop(String s)
            {
                list.add(0, s);
                lowercase.add(0, s.toLowerCase());
            }

            void remove(int index)
            {
                list.remove(index);
                lowercase.remove(index);
            }

            List<String> getList()
            {
                return list;
            }

            List<String> getFiltered()
            {
                if (filtered == null)
                {
                    filtered = list;
                }
                return filtered;
            }

            int size()
            {
                return list.size();
            }

            void setPattern(String pattern)
            {
                if (pattern == null || pattern.isEmpty())
                {
                    filtered = list;
                    AutoCompleteCombobox.this.setSelectedItem(model.getElementAt(0));
                    //logger.debug(String.format("[setPattern] combo.setSelectedItem(null)"));
                }
                else
                {
                    filtered = new ArrayList<>(limit);
                    pattern = pattern.toLowerCase();
                    for (int i = 0; i < lowercase.size(); i++)
                    {
                        //case insensitive search
                        if (lowercase.get(i).contains(pattern))
                        {
                            filtered.add(list.get(i));
                        }
                    }
                    AutoCompleteCombobox.this.setSelectedItem(pattern);
                    //logger.debug(String.format("[setPattern] combo.setSelectedItem(%s)", pattern));
                }
                //logger.debug(String.format("pattern:'%s', filtered: %s", pattern, filtered));
            }

            boolean contains(String s)
            {
                if (s == null || s.trim().isEmpty())
                {
                    return true;
                }
                s = s.toLowerCase();
                for (String item : lowercase)
                {
                    if (item.equals(s))
                    {
                        return true;
                    }
                }
                return false;
            }
        }
    }
}

